NEAR | Robert Yan:基于 Rust 的智能合约开发框架的比较
9 月 4 日的 BeWater DevCon 2021 全球开发者大会上,NEAR 亚洲技术负责人 Robert Yan,从代码层面比较了 Solidity、NEAR、ink!、CosmWasm 和 Solana 的智能合约。
通过分析,Rust 的智能合约具有2个特点:
1、从代码实例来看,Rust 智能合约编程语言和 Solidity 语言在语法、语言和数据结构等方面是比较接近的。
2、Rust 智能合约编程语言具备更好的类型安全与内存安全,字节码非常高效的同时体积很小。
BeWater
一、什么是智能合约?
虽然叫智能合约,但是它其实并不 “smart”。我认为叫 Automated Agreement(翻译:自动化协议)可能会更贴近一点,不一定准确。
1990 年初,Nick Szabo 对智能合约作过一个定义:
a set of promises, specified in digital form, including protocols within which the parties perform on these promises.
(翻译:一组以数字形式指定的承诺,包括各方履行这些承诺的协议)
我们现在常见的智能合约一般是区块链上的应用,且去掉了中间人/中介。
比现有的传统系统显得更加便宜、更加快,也更安全,比如在金融领域或其他领域。
还有大家经常提到的一个特点就是可组合性。
接着,再对比下智能合约和应用链(AppChain),可以更加清楚地看到智能合约存在的一些问题:
智能合约只能被动地被调用,所以其实并没有自主的智能。
只能在特定的虚拟机或环境中运行,存在限制。
无法配置网络参数,以符合每个应用的特定需求。
相比智能合约,应用链可以配置网络参数,但同时也存在一些缺点:
在启动之前需要有一个自己的社区和生态
启动成本比常规的智能合约高很多
目前状态下也不太具备可组合性,跨链交互协议也还处于非常原始的状态
BeWater
二、为什么选择Rust?
Solidity 是否真的 “Solid” ?Solidity 主要的问题有哪些?
台下就有同学就提出:Solidity 原生的 256 位的字长有点过度占用空间,EVM 也存在一些问题,合约调用时有些语义有时候容易造成漏洞。
的确,Solidity 存在着各种问题,但目前阶段其实瑕不掩瑜,毕竟它是以太坊最主流的语言。
说到语言和语言之间的比较,先说一下不同的运行环境之间的比较。现在,另外一种逐渐流行的运行环境是基于 WebAssembly 的。所以,先来比较一下 EVM 和 WASM:
EVM 的优点主要包括:
很强的确定性、可终止的、隔离的环境
是目前最流行的运行环境,各大公链和二层网络都会去支持它
但 EVM 主要的问题包括:
效率低
支持的语言和工具比较有限
较小的生态系统
相比之下,WebAssembly 具有以下优点:
内存安全、沙箱环境和平台独立性
高性能
更小的二进制文件
支持更多语言,包括 Rust、Go、AssemblyScript 等
但 WebAssembly 在区块链领域的应用相对少很多,这是我们可以看到的。
接着,再从语言层面来对比下 Solidity 和 Rust:
Solidity 最主要的的缺陷就是安全性不太好,但优点很多。Solidity 跟 JavaScript 有点类似,虽然 JavaScript 经常被人吐槽,但大家依然还是一直在用它,Solidity 也是类似情况,目前已经占据了第一名的位置。Solidity 语法上和 ECMAScript 比较接近,所以易于学习。也有对类型的支持,有很好的继承支持,对合约开发确实非常友好。
Rust 相对来说也有自己的一些优点,比如它有很强的类型安全、内存安全,也不会有意外的 behaviors。它的社区和生态现在发展也非常快,字节码也非常高效,体积非常小。但 Rust 普遍被诟病的问题就是它的学习曲线非常陡峭,很多人会望而生畏。
BeWater
三、基于 Rust 的智能合约
接下来给大家介绍一下常用的基于 Rust 的智能合约开发框架有哪些,这里我会以 NEAR、ink!、CosmWasm、Solana 为例。其实还有很多,但因为时间关系,就以这几个为例。
这里列了一些我们会去做评估的标准。详细来说,我们可以做非常完整的评估。比如说我们可以从协议角度来讲,它有哪些特性?它为什么这样去设计它的合约开发工具?怎么去初始化合约?怎么调用合约或者跨合约调用?它和账户模型、数据模型、协议模型有哪些关联?怎么做测试?怎么部署?如果要做完整的评估报告,这些都需要加进来。
但是今天演讲时间关系我们不会把所有的都放到上面来讲,我们主要还是看一下代码,让大家有一个直观的感受,后面有兴趣可以进一步看相关的资料进行学习。
Solidity
我们还是以 Solidity 为例,给出一个作为项目模板的例子来做一个比较,这样后面对比的时候会比较清楚。
该例子的 Solidity 合约代码,请点击“阅读原文”中查看。
这个例子叫 ContentTracker,相当于我们有一个网站,上面的一些路径可以放一些广告,其实就等于每个路径都是一个广告位,可以被竞拍。
里面的 ContentRecord 数据结构,保存了广告位的价格、内容和拍卖者。另外,有个 map 保存了每个路径下的 ContentRecord。还有个 contractOwner,我们会有对应的方法去使用它。
这个例子支持了一个竞拍模型,你可以去买广告位,所以它有 purchase() 方法,可传入路径和内容作为参数。方法里会去检查你的出价是多少,出价是否高于该广告位目前已经有的价格,如果是的话,会把它记录下来,之前已经有人买了会把之前的钱退给前一个 Owner ,会把最新的记录,包括价格、内容和 Owner 改成最新的出价人。
另外还有一个 withdraw() 函数可供 contractOwner 调用,为了把赚到的钱拿回来。这个也是为了后面做比较的时候,会用这个函数去看一下那些 Rust 的智能合约开发环境的特点。
还有 getRoute() 函数可以拿到对应的 route 下面的广告词是什么。
这个示例合约一共就 69 行代码,还是比较简单的合约,也没做很多复杂的考虑。
这个例子中,有几个 Highlights,包括使用了 key-value 存储的 mapping,purchase() 函数添加了 payable 修饰器,withdraw() 函数限定了 onlyOwner 修饰器,getRoute() 函数则带有 view 修饰。
NEAR
NEAR 是一个 PoS 的公链,它的主要特点是实现了叫「夜影协议」的分片技术。它的合约开发主要还是基于 Rust 和 AssemblyScript,在 WASM 的运行环境里运行。它采用了水平分片技术,理论上可以做动态的重新分片。基于这种分片的特点可以支持异步的跨合约调用,和 Solidity 的同步调用会有一定的差异。
其他还有很多特点,但因为时间关系,我们直接看代码。
NEAR 的 ContentTracker 合约实现代码,请点击“阅读原文”中查看。
可以看到它头部引用了几个库,包括序列化和反序列化的库,还有 LookupMap 这种数据结构,该结构是和 Solidity 的 mapping 一样存储 key-value 值。
下面定义了 ContentRecord 数据结构,和前面 Solidity 的一样,会有 price、content 和 Owner 变量。
然后下面有个 #[near_bindgen] 注解,这是定义了合约的根结构 ContentTracker,里面存了 values 变量,类型和前面 Solidity 的 mapping 类似,以及 contract_owner 变量。
接着的 Default 实现等价于 Solidity 的 constructor。
purchase() 方法添加了 #[payable] 注解,也等价于 Solidiy 的 payable。
env::attached_deposit() 也和 Solidity 的 msg.value 一样的功能,可读取到转账金额。之后就是对 deposit 做判断,逻辑和前面的其实一样。
转账时,使用了 Promise::new(...).transfer(...),这里其实是一个异步调用。
代码行数总共 75 行,比前面 Solidity 的例子多了几行,但可以看到 Near 的这个智能合约开发还是相当简洁的,只要用原生的 Rust 语言就可以去开发和测试,所以还是支持得比较好。
ink!
ink! 是什么?它是一个在 AppChain 里面,在 Substrate 的环境里面可以运行的 Contract Pallet,是由 Parity 团队实现的。如果是在波卡生态,可以在平行链里跑你的合约。如果是在 Octopus,也可以用租赁安全的方式去启动应用链,也可以在上面执行你的合约。Contract Pallet 也是基于 WebAssembly 用 Rust 语言实现的。
ink! 的特点,不像以太坊和 NEAR 一样的全局共享状态,而是在 AppChain 或平行链里具有一定的可组合性。
另外,如果以后 XCMP 支持比较好的话,有可能也可以实现跨链的智能合约的交互。
案例相关文档,请点击“阅读原文”中查看。
可以看到 ink! 的实现跟 NEAR 比较接近,但是它会有点不一样。比如,第一,它会用 module 的方式来实现。另外,可以看到还使用了不同的宏的实现。刚才我们看到 NEAR 可以用 payable 这种宏,初始化时用到 default,而 ink! 也是类似的,可以加上 #[ink(event)] 注解来定义 event,用 #[ink(topic)] 就和 Solidity 事件中的 indexed 一样可以索引日志。
用 #[ink(storage)] 来定义合约的根结构,和 NEAR 的实现差不多。另外,ink! 还可以定义错误类型,这是在之前的示例中没有用到的,也包括刚才提到的 event。#[ink(constructor)] 则定义了初始化方法,#[ink(message)] 定义则可以读取状态,再增加 payable 就可以进行付费。
withdraw() 的实现稍微复杂一些,因为还做了错误处理,所以没有像 NEAR 的那样简洁。
一共用了 109 行代码,和前面 NEAR 的实现还是挺接近的,我觉得也是挺优雅的实现。
CosmWasm
接下来看看 CosmWasm,这是一个 Cosmos-SDK module,它提供了一种方法,可以在 Cosmos Zone 里面去运行你的智能合约。Cosmos 和基于 Substrate 的应用链,两者的开发环境还是比较接近的。
CosmWasm 从名字也可以看出来,是基于 WASM 的运行环境。另外,虽然 Cosmos 主要是用 Go 语言开发的,但也支持 Rust 的开发环境。
CosmWasm 有一点比较特别,就是把同步调用的跨合约调用给禁止掉了,主要是为了严格防止重入攻击。
另外,CosmWasm 也支持 IBC,所以也可以做一些跨链的操作,但这个可能还需要做更多工作去完善。
代码层面比前面两种会复杂很多,首先它不是单独的文件而是多文件的,包括了 contract.rs、error.rs、lib.rs、msg.rs、state.rs 共 5 个文件。
state.rs 代码,请点击“阅读原文”中查看。
这里面主要定义了一些数据结构,比如 ContentRecord,以及 contractOwner 的
config,还有一些序列化对应的读写的操作。
error.rs 的代码,定义了一个枚举的错误类型,具体请点击“阅读原文”中查看。
可以看到,需要你手动写很多代码,需要自己实现 handler,需要手动写 msg 的处理。这边用到宏的地方比较少,因为它原生没有使用 procedural macro 的定义,所以需要根据原生的 SDK 做很多方法的调用,即需要自己手动写很多代码,但它同样可以实现功能。相对来说会繁琐一些,但基本上也是能实现的。
Solana
最后来讲一下 Solana,它也是可以用 Rust、C 和 C++ 来实现的智能合约。
Solana 有个很好的特性,就是可以支持并发合约的执行,这是其他智能合约不具备的。
另外,Solana 一般使用 Anchor 来开发,如果用原生开发,工作量会比较大。而且,Solona 的智能合约不叫合约,叫 program。
不过,使用 Solana 开发智能合约其实也面临着一些挑战:
account 的存储大小是固定的,需要一开始就指定大小
函数没有返回值,要读取数据需要通过 off chain 的方式去读取,然后自己做一些解析。
原生不支持 key-value 的 map,也为开发带来了些挑战。
相关代码实现,请点击“阅读原文”中查看。
另外刚才也提到几个关键词,用来指定 Solana 特定的需要,来和它智能合约的模型做一些适配。
总结
今天介绍的例子大概就是这样,希望对大家去了解基于 Rust 的智能合约的开发会有一定的帮助。我其实没有做很详细的比较,但是通过这个过程可以看到它和 Solidity 的开发还是比较接近。基本上的模型以及用到的工具,虽然会有一些差异,但是从语法、语言和数据结构这些角度还是非常接近的,特别像 ink! 和 NEAR,其实现还是非常简洁和优雅。
今天 Rust 的介绍就到这里,我们可能还会问的是说未来智能合约语言会往哪个方向去走,其实我没有什么答案。
Solidity 它是什么时候发明的,好像是 2014 年,到今天大概 7 年左右的时间,未来 5 年可能也会有些新的趋势,像 Rust 的智能合约我觉得也值得大家进一步关注,因为 Rust 其实也有挺不错的开发体验,特别像 NEAR、ink! 都建议大家尝试和了解的,我的分享就到这里。
————————————————
历史文章:
7、3Box Joel | 去中心化数字身份与数据,未来在何方?
- 闭门会报名 -